package chloroform

import _root_.util.ColUtils
import _root_.util.logging.Logged
import java.util.concurrent.locks.ReentrantLock
import collection.mutable.{HashMap, SynchronizedMap}
import l10n.{ProjectLocale, ResourceBundleProjectMetaDataSource}
import scaladuct.{ExtensibleDecorator, Extensible}
import scaladuct.l10n.AbstractL10nProperties
import java.util.{Collections, Locale}
import argo.jdom.{JsonNodeFactories, JsonStringNode, JsonNode}
import java.lang.{IllegalArgumentException, String}

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Feb 17, 2011
 * Time: 7:44:59 PM
 */
@serializable
@SerialVersionUID(1)
trait Project extends Extensible {
  def getProjectLoader(): ProjectLoader

  def getProjectName(): String

  def getEntries(): List[ProjectEntry]

  def getEntryNames(): List[String]

  def getEntry(entryName: String): ProjectEntry

  def getScenarios(): Set[String]

  def getEntriesForScenario(scenario: String): List[ProjectEntry] = {
    getEntries.filter(_.scenarios.contains(scenario))
  }

  def findInPath(path: String): ProjectEntry = {
    val pathSteps = path.split(",")
    val rootStep = pathSteps.head
    val extSteps = pathSteps.tail

    var entry: ProjectEntry = getEntry(rootStep)

    extSteps.foreach(extStep => {
      entry.extensions.find(_.name == extStep) match {
        case None => error("extension " + extStep + " not found")
        case Some(ext) => {
          entry = ext
        }
      }
    })

    entry
  }
}

trait ProjectSaver extends Project {
  def save()
}

class ProjectDecorator(decor: Project) extends Project with ExtensibleDecorator {
  def decoratedForExtensible = decor

  def getProjectName() = decor.getProjectName

  def getEntries() = decor.getEntries

  def getEntryNames() = decor.getEntryNames()

  def getEntry(entryName: String) = decor.getEntry(entryName)

  def getScenarios() = decor.getScenarios

  def getProjectLoader() = decor.getProjectLoader
}

trait MutableProject extends Project {
  def createProjectEntry(packagedArgs: ProjectEntry): ProjectEntry

  def addEntry(entry: ProjectEntry, replaceAllowed: Boolean)

  def removeEntry(entryName: String)

  def lock()

  def unlock()

  def removeInPath(path: String) {
    val entry: ProjectEntry = findInPath(path)
    if (entry.extendedEntry == null) {
      removeEntry(entry.name)
    } else {
      entry.extendedEntry.removeExtension(entry.name)
    }
  }

}

@serializable
@SerialVersionUID(1L)
class ProjectRef(val projectName: String, val projectCache: ProjectCache) {

  def getProject() =
    projectCache.getProject(projectName)

  def getScenarios(): Array[String] = getProject.getScenarios.toArray

}

@serializable
@SerialVersionUID(1)
class ProjectCache(projectLoader: ProjectLoader, loc: Locale) {

  //private[this] var loader: ProjectLoader = new JsonProjectLoader(new ClasspathResourceResolver(classOf[Project].getClassLoader))

  //  private[chloroform] def setProjectLoader(projectLoader: ProjectLoader) {
  //    this.loader = projectLoader
  //  }

  //  private[chloroform] def setResolver(res: ResourceResolver) {
  //    resolver = res
  //  }

  private[this] val projects = new HashMap[String, Project] with SynchronizedMap[String, Project]

  //private[this] var projectLocks = Map[String, ReentrantLock]()
  private[this] val mainLock = new ReentrantLock

  def reloadProject(projectName: String): Project = {
    mainLock.lock
    try {
      projects.remove(projectName)
      getProject(projectName)
    } finally {
      mainLock.unlock
    }
  }

  def getProjectRef(projectName: String): ProjectRef = {
    new ProjectRef(projectName, this)
  }

  def getProject(projectName: String): Project with ProjectLocale = {
    getProject(projectName, (proj: Project) => new ProjectDecorator(proj)
      with ResourceBundleProjectMetaDataSource
      with AbstractL10nProperties
      with ProjectLocale {
      val bundleName = projectName
      val bundlePrefix = ""
      val locale = loc
    }).asInstanceOf[Project with ProjectLocale]
  }

  private def getProject(projectName: String, decorator: (Project) => Project = null): Project = {
    def getProject(): Project = {
      val project = projects.get(projectName) match {
        case Some(project) => project
        case None => {
          loadProject(projectName, decorator)
        }
      }
      project
    }

    mainLock.lock
    try {
      getProject
    } finally {
      mainLock.unlock
    }

//    var projectLock: ReentrantLock = null
//    mainLock.lock
//    try {
//      projectLock = projectLocks.get(projectName) match {
//        case Some(lock) => lock
//        case None => {
//          val projectLock = new ReentrantLock
//          projectLocks += (projectName -> projectLock)
//          projectLock
//        }
//      }
//    } finally {
//      mainLock.unlock
//    }
//
//    projectLock.lock
//    try {
//      getProject
//    } finally {
//      projectLock.unlock
//    }
  }

  private def loadProject(projectName: String, decorator: (Project) => Project): Project = {
    projects.get(projectName) match {
      case Some(project) => project
      case None => {
        val project = loadProjectFromRepository(projectName)
        val decorated = if (decorator != null)
          decorator(project)
        else project

        projects += (projectName -> decorated)
        decorated
      }
    }
  }

  private def loadProjectFromRepository(projectName: String): Project = {
    projectLoader.loadProject(projectName)
  }
}

@serializable
@SerialVersionUID(1L)
class ProjectCookie(val pageId: String, props: java.util.Map[JsonStringNode, JsonNode]) {

  private var internalProperties = new java.util.HashMap[JsonStringNode, JsonNode](props)

  def this(cookie: ProjectCookie) = this(cookie.pageId, cookie.internalProperties)

  def properties(): java.util.Map[JsonStringNode, JsonNode] = Collections.unmodifiableMap(internalProperties)

  def setProperty(propertyName: String, valueNode: JsonNode) = {
    internalProperties.synchronized {
      internalProperties.put(JsonNodeFactories.aJsonString(propertyName), valueNode)
    }
  }

  def getProperty(propertyName: String) = {
    var valueNode: JsonNode = null
    internalProperties.synchronized {
      valueNode = internalProperties.get(JsonNodeFactories.aJsonString(propertyName))
    }
    valueNode
  }
}

@serializable
@SerialVersionUID(1L)
class ProjectEntry(val name: String,
                   val factory: String,
                   val isPublic: Boolean,
                   val scenarios: Set[String],
                   val cookies: List[ProjectCookie],
                   @volatile private[this] var extList: List[ProjectEntry],
                   @volatile var extendedEntry: ProjectEntry = null) extends Logged {

  def this(entry: ProjectEntry) = this(entry.name, entry.factory, entry.isPublic, entry.scenarios,
    entry.cookies, entry.extensions, entry.extendedEntry)

  def isInScenario(scenario: String) = {
    val res = this.scenarios.find(_ == scenario) != None
    res
  }

  def fullName: String = if (extendedEntry == null) {
    name
  } else {
    extendedEntry.fullName + "," + name
  }

  def extensions() = extList

  def addExtension(extEntry: ProjectEntry) {
    extEntry.extendedEntry = this
    extList = extEntry :: extensions
  }

  def removeExtension(extName: String) {
    extList = extensions.filterNot(_.name == extName)
  }

  def moveExtension(extName: String, delta: Int) {
    try {
      extList = ColUtils.moveInList[ProjectEntry](extensions, delta, x => x.fullName == extName)
    }
    catch {
      case iae: IllegalArgumentException => warn(iae.getMessage)
    }
  }

  def replaceExtension(extEntry: ProjectEntry) {
    extList = extensions.map(ext => {
      if (ext.name == extEntry.name) {
        extEntry.extendedEntry = this
        extEntry
      } else {
        ext
      }
    })
  }
}
